﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using System.IO;

namespace MirrorFolder
{
    public partial class FormRecover : Form
    {
        string archDirPath;

        class RDir
        {
            public bool IsRoot { get { return ParentDir == null; } }
            public DirSync.FileCmp Info = null;
            public List<RDir> DirL = new List<RDir>();
            public RDir ParentDir = null;
            public List<DirSync.FileCmp> Files = new List<DirSync.FileCmp>();
            public RDir(RDir ParentDir, DirSync.FileCmp Info)
            {
                this.ParentDir = ParentDir;
                this.Info = Info;
            }
            public RDir()
            {

            }

            public override string ToString()
            {
                if (IsRoot) return "";
                return ParentDir.ToString() + Info.Name + "\\";
            }

        }

        List<DirSync.ArchiveLine> aLines;
        /// <summary>дерево дат</summary>
        List<ArchYear> aTree;

        #region *** дерево дат *********************************

        class ArchYear
        {
            public int Val;
            public List<ArchMonth> Month = new List<ArchMonth>();
            public ArchYear(int val) { this.Val = val; }
            public override string ToString()
            {
                return Val.ToString();
            }
        }

        class ArchMonth
        {
            public int Val;
            public List<ArchDay> Day = new List<ArchDay>();
            public ArchYear ParentYear;
            public ArchMonth(int val, ArchYear parentYear) { this.Val = val; this.ParentYear = parentYear; }
            public override string ToString()
            {
                return Val.ToString();
            }
        }

        class ArchDay
        {
            public int Val;
            public List<Arch> Archives = new List<Arch>();
            public ArchMonth ParentMonth;
            public ArchDay(int val, ArchMonth parentMonth) { this.Val = val; this.ParentMonth = parentMonth; }
            public override string ToString()
            {
                return string.Format("{0}.{1}.{2}", Val, ParentMonth.Val, ParentMonth.ParentYear.Val);
            }
        }

        class Arch
        {
            public DirSync.ArchiveEl ArchEl;
            public DirSync.ArchiveLine ArchLine;
            public ArchDay ParentDay;
            public RDir Dir = null;
            public DirSync.Snapshot Sn = null;
            public Arch(DirSync.ArchiveEl archEl, DirSync.ArchiveLine archLine, ArchDay parentDay)
            {
                this.ArchEl = archEl;
                this.ArchLine = archLine;
                this.ParentDay = parentDay;
            }

            public void FillRDir(string archDirPath)
            {
                if (Dir == null)
                {
                    Sn = new DirSync.Snapshot(Path.Combine(Path.Combine(archDirPath, ArchLine.DirName), "FULL"));
                    if (ArchEl.DirName.ToUpper().Trim() != "FULL")
                    {
                        if (ArchLine.ArchMode == ArchiveMode.Diff)
                        {
                            DirSync.Snapshot snDiff = new DirSync.Snapshot(Path.Combine(Path.Combine(archDirPath, ArchLine.DirName), ArchEl.DirName));
                            Sn = DirSync.GetArchSnapshot(Sn, snDiff, ArchEl.DelDirs, ArchEl.DelFiles);
                        }
                        else
                        {
                            for (int i = 1; i < ArchLine.ArchL.Count; i++)
                            {
                                DirSync.Snapshot snInc = new DirSync.Snapshot(Path.Combine(Path.Combine(archDirPath, ArchLine.DirName), ArchLine.ArchL[i].DirName));
                                Sn = DirSync.GetArchSnapshot(Sn, snInc, ArchLine.ArchL[i].DelDirs, ArchLine.ArchL[i].DelFiles);
                                if (ArchLine.ArchL[i].DirName == ArchEl.DirName) break;
                            }
                        }
                    }
                    Dir = GetRDir(Sn);
                }
            }

            /// <summary>Преобразовать снапшот в каталог для восстановления</summary>
            RDir GetRDir(DirSync.Snapshot sn)
            {
                RDir ret = new RDir();
                int level = 1;
                bool nextLevel = true;
                while (nextLevel)
                {
                    nextLevel = false;
                    foreach (DirSync.FileCmp dir in sn.DList)
                    {
                        string[] sl = dir.LocalPath.Trim().Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
                        if (sl.Length > level) { nextLevel = true; continue; }
                        if (sl.Length < level) continue;
                        RDir actDir = ret;
                        for (int i = 0; i < (sl.Length - 1); i++) actDir = actDir.DirL.First(chDir => chDir.Info.Name == sl[i]);
                        actDir.DirL.Add(new RDir(actDir, dir));
                    }
                    level++;
                }

                level = 1;
                nextLevel = true;
                while (nextLevel)
                {
                    nextLevel = false;
                    foreach (DirSync.FileCmp file in sn.FList)
                    {
                        string[] sl = file.LocalPath.Trim().Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
                        if (sl.Length > level) { nextLevel = true; continue; }
                        if (sl.Length < level) continue;
                        RDir actDir = ret;
                        for (int i = 0; i < (sl.Length - 1); i++) actDir = actDir.DirL.First(chDir => chDir.Info.Name == sl[i]);
                        actDir.Files.Add(file);
                    }
                    level++;
                }

                return ret;
            }

            public override string ToString()
            {
                return ArchEl.CreateTime.ToString("dd.MM.yyyy HH:mm:ss");
            }
        }

        #endregion *** дерево дат *********************************

        public FormRecover()
        {
            InitializeComponent();
        }

        public void ShowDialog(string archDirPath)
        {
            this.archDirPath = archDirPath;
            ShowDialog();
        }

        private void FormRecover_Shown(object sender, EventArgs e)
        {
            SetLoadData(true);
            Application.DoEvents();

            // получение всех линий архива
            aLines = DirSync.GetArchiveLines(archDirPath, false);
            aTree = GetArchTree();
            if (aTree.Count > 0)
            {
                SelY = aTree[aTree.Count - 1];
                SelM = SelY.Month[SelY.Month.Count - 1];
                SelD = SelM.Day[SelM.Day.Count - 1];
                SelArch = SelD.Archives[SelD.Archives.Count - 1];
            }
            RefillTimeLine();

            RefillListData();

            SetLoadData(false);
        }

        void SetLoadData(bool loadData) { SetLoadData(loadData, false); }
        void SetLoadData(bool loadData, bool isResrore)
        {
            lblWait.Text = isResrore ? "Восстановление данных" : "Загрузка данных";
            lblWait.Visible = loadData;
            pnlData.Enabled = !loadData;
            pnlTimeline.Enabled = !loadData;
            btnClose.Enabled = !loadData;
            btnRestore.Enabled = !loadData;
        }

        RDir ActDir = null;

        void RefillListData()
        {
            lvData.Items.Clear();
            lblPath.Text = "";
            btnDirUp.Enabled = false;
            if (SelArch == null) return;

            SetLoadData(true);
            Application.DoEvents();
            if (ActDir == null)
            {
                SelArch.FillRDir(archDirPath);
                ActDir = SelArch.Dir;
            }
            btnDirUp.Enabled = !ActDir.IsRoot;
            lblPath.Text = SelArch.ArchEl.CreateTime.ToString("dd.MM.yyyy HH:mm") + "\\" + ActDir.ToString();

            List<ListViewItem> lstDir = new List<ListViewItem>();
            List<ListViewItem> lstFile = new List<ListViewItem>();

            // каталоги
            foreach (RDir dir in ActDir.DirL)
            {
                ListViewItem itm = new ListViewItem(dir.Info.Name, 0);
                itm.Tag = dir;
                itm.SubItems.Add("");
                itm.SubItems.Add("<папка>");
                itm.SubItems.Add(dir.Info.ChangeDate.ToString("dd.MM.yyyy HH:mm"));
                lstDir.Add(itm);
            }
            lstDir.Sort((dir1, dir2) => dir1.Text.CompareTo(dir2.Text));

            // файлы
            foreach (DirSync.FileCmp file in ActDir.Files)
            {
                // получение иконки
                ListViewItem itm = new ListViewItem(Path.GetFileNameWithoutExtension(file.Name), GetIcoNum(file.Path));
                itm.Tag = file;
                itm.SubItems.Add(file.Ext);
                itm.SubItems.Add(file.Len.ToString());
                itm.SubItems.Add(file.ChangeDate.ToString("dd.MM.yyyy HH:mm"));
                lstFile.Add(itm);
            }
            lstDir.Sort((file1, file2) => file1.Text.CompareTo(file2.Text));

            List<ListViewItem> lst = new List<ListViewItem>(lstDir); lst.AddRange(lstFile);
            lvData.Items.AddRange(lst.ToArray());

            SetLoadData(false);
        }

        /// <summary>словарь номеров в листе иконок для расширений файлов</summary>
        Dictionary<string, int> extIcoNums = new Dictionary<string, int>();

        /// <summary>получение номера иконки в листе иконок по расширению файла</summary>
        int GetIcoNum(string filePath)
        {
            string EXT = Path.GetExtension(filePath).ToUpper().Trim();
            if (extIcoNums.ContainsKey(EXT)) return extIcoNums[EXT];
            //imList.Images.Add(Icon.ExtractAssociatedIcon( filePath));
            imList.Images.Add(GetIconOldSchool(filePath));

            extIcoNums.Add(EXT, imList.Images.Count - 1);
            return imList.Images.Count - 1;
        }

        #region *** Old-School method ******************************
        [System.Runtime.InteropServices.DllImport("shell32.dll")]
        static extern IntPtr ExtractAssociatedIcon(IntPtr hInst,
           System.Text.StringBuilder lpIconPath, out ushort lpiIcon);

        public static Icon GetIconOldSchool(string fileName)
        {
            ushort uicon;
            System.Text.StringBuilder strB = new System.Text.StringBuilder(fileName);
            IntPtr handle = ExtractAssociatedIcon(IntPtr.Zero, strB, out uicon);
            Icon ico = Icon.FromHandle(handle);
            return ico;
        }
        #endregion *** Old-School method ******************************

        private void lvData_SelectedIndexChanged(object sender, EventArgs e)
        {

        }

        private void lvData_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            if (lvData.SelectedIndices.Count == 0) return;
            if (lvData.SelectedItems[0].Tag is RDir)
            {
                ActDir = (RDir)lvData.SelectedItems[0].Tag;
                RefillListData();
            }
        }

        private void btnDirUp_Click(object sender, EventArgs e)
        {
            ActDir = ActDir.ParentDir;
            RefillListData();
        }

        #region *** заполнение линии времени ********************************************

        ArchYear SelY = null;
        ArchMonth SelM = null;
        ArchDay SelD = null;
        Arch SelArch = null;

        void RefillTimeLine()
        {
            #region *** начальное заполнение *********
            if (tMonth.Items.Count == 0)
            {
                string[] m = new string[] { "янв", "фев", "мар", "апр", "май", "июн", "июл", "авг", "сен", "окт", "ноя", "дек" };
                for (int i = 0; i < 12; i++)
                {
                    ToolStripButton btn = new ToolStripButton();
                    btn.Text = m[i];
                    btn.Enabled = false;
                    btn.Click += new EventHandler(btnMonth_Click);
                    tMonth.Items.Add(btn);
                }
            }
            if (tDays.Items.Count == 0)
            {
                for (int i = 1; i <= 31; i++)
                {
                    ToolStripButton btn = new ToolStripButton();
                    btn.Text = i.ToString("00");
                    btn.Enabled = false;
                    btn.Click += new EventHandler(btnDay_Click);
                    tDays.Items.Add(btn);
                }
            }

            // заполнение лет
            if (tYears.Items.Count == 0)
            {
                tYears.Items.Clear();
                for (int i = aTree.Count - 1; i >= 0; i--)
                {
                    ToolStripButton btn = new ToolStripButton();
                    btn.Text = aTree[i].Val.ToString();
                    btn.Tag = aTree[i];
                    btn.Click += new EventHandler(btnYear_Click);
                    tYears.Items.Add(btn);
                }
            }

            #endregion *** начальное заполнение *********

            #region *** установка видимости месяцев и дней *********

            foreach (ToolStripItem itm in tMonth.Items)
            {
                itm.Enabled = false;
                itm.Tag = null;
            }
            if (SelY != null)
            {
                foreach (ArchMonth m in SelY.Month)
                {
                    tMonth.Items[m.Val - 1].Enabled = true;
                    tMonth.Items[m.Val - 1].Tag = m;
                }
            }

            foreach (ToolStripItem itm in tDays.Items)
            {
                itm.Enabled = false;
                itm.Tag = null;
            }
            if (SelM != null)
            {
                foreach (ArchDay d in SelM.Day)
                {
                    tDays.Items[d.Val - 1].Enabled = true;
                    tDays.Items[d.Val - 1].Tag = d;
                }
            }

            #endregion *** установка видимости месяцев и дней *********

            #region *** заполнение дат *********

            tDates.Items.Clear();
            if (SelD != null)
            {
                for (int i = SelD.Archives.Count - 1; i >= 0; i--)
                {
                    ToolStripButton btn = new ToolStripButton();
                    btn.Text = SelD.Archives[i].ArchEl.CreateTime.ToString("HH:mm");
                    btn.Click += new EventHandler(btnDate_Click);
                    btn.Tag = SelD.Archives[i];
                    tDates.Items.Add(btn);
                }
            }
            #endregion *** заполнение дат *********

            #region *** установка селекта *********
            // год
            foreach (ToolStripItem itm in tYears.Items) (itm as ToolStripButton).Checked = false;
            if (SelY != null) foreach (ToolStripItem itm in tYears.Items) (itm as ToolStripButton).Checked = itm.Text == SelY.Val.ToString();
            // месяц
            foreach (ToolStripItem itm in tMonth.Items) (itm as ToolStripButton).Checked = false;
            if (SelM != null) (tMonth.Items[SelM.Val - 1] as ToolStripButton).Checked = true;
            // день
            foreach (ToolStripItem itm in tDays.Items) (itm as ToolStripButton).Checked = false;
            if (SelD != null) (tDays.Items[SelD.Val - 1] as ToolStripButton).Checked = true;
            // дата
            foreach (ToolStripItem itm in tDates.Items) (itm as ToolStripButton).Checked = false;
            if (SelArch != null) foreach (ToolStripItem itm in tDates.Items) (itm as ToolStripButton).Checked = (itm.Tag as Arch).ArchEl.CreateTime == SelArch.ArchEl.CreateTime;
            #endregion *** установка селекта *********

        }

        /// <summary>Получение выбранной даты. Если дата не выбрана, возвращает MinValue</summary>
        DateTime GetSelDate()
        {
            int selYear = GetSelButtonInd(tYears);
            if (selYear == -1) return DateTime.MinValue;
            selYear = Convert.ToInt32((tYears.Items[selYear] as ToolStripButton).Text);

            int selMonth = GetSelButtonInd(tMonth) + 1;
            if (selMonth == 0) return DateTime.MinValue;

            int selDay = GetSelButtonInd(tDays) + 1;
            if (selDay == 0) return DateTime.MinValue;

            int ind = GetSelButtonInd(tDates);
            if (ind == -1) return DateTime.MinValue;
            string[] selTime = (tYears.Items[selYear] as ToolStripButton).Text.Split(':');
            int h = Convert.ToInt32(selTime[0]);
            int m = Convert.ToInt32(selTime[1]);

            return new DateTime(selYear, selMonth, selDay, h, m, 0);
        }

        private void btnYear_Click(object sender, EventArgs e)
        {
            if ((sender as ToolStripButton).Checked) return;
            SelY = (ArchYear)(sender as ToolStripButton).Tag;
            SelM = SelY.Month[SelY.Month.Count - 1];
            SelD = SelM.Day[SelM.Day.Count - 1];
            SelArch = SelD.Archives[SelD.Archives.Count - 1];
            RefillTimeLine();
            ActDir = null;
            RefillListData();
        }

        private void btnMonth_Click(object sender, EventArgs e)
        {
            if ((sender as ToolStripButton).Checked) return;
            SelM = (ArchMonth)(sender as ToolStripButton).Tag;
            SelD = SelM.Day[SelM.Day.Count - 1];
            SelArch = SelD.Archives[SelD.Archives.Count - 1];

            RefillTimeLine();
            ActDir = null;
            RefillListData();
        }

        private void btnDay_Click(object sender, EventArgs e)
        {
            if ((sender as ToolStripButton).Checked) return;
            SelD = (ArchDay)(sender as ToolStripButton).Tag;
            SelArch = SelD.Archives[SelD.Archives.Count - 1];
            RefillTimeLine();
            ActDir = null;
            RefillListData();
        }

        private void btnDate_Click(object sender, EventArgs e)
        {
            if ((sender as ToolStripButton).Checked) return;
            SelArch = (Arch)(sender as ToolStripButton).Tag;
            RefillTimeLine();
            ActDir = null;
            RefillListData();
        }

        #endregion *** заполнение линии времени ********************************************

        List<ArchYear> GetArchTree()
        {
            List<ArchYear> ret = new List<ArchYear>();

            foreach (DirSync.ArchiveLine line in aLines)
            {
                foreach (DirSync.ArchiveEl el in line.ArchL)
                {
                    ArchYear aYear = ret.FirstOrDefault(y => y.Val == el.CreateTime.Year);
                    if (aYear == null) { ret.Add(new ArchYear(el.CreateTime.Year)); aYear = ret[ret.Count - 1]; }
                    ArchMonth aMonth = aYear.Month.FirstOrDefault(y => y.Val == el.CreateTime.Month);
                    if (aMonth == null) { aYear.Month.Add(new ArchMonth(el.CreateTime.Month, aYear)); aMonth = aYear.Month[aYear.Month.Count - 1]; }
                    ArchDay aDay = aMonth.Day.FirstOrDefault(y => y.Val == el.CreateTime.Day);
                    if (aDay == null) { aMonth.Day.Add(new ArchDay(el.CreateTime.Day, aMonth)); aDay = aMonth.Day[aMonth.Day.Count - 1]; }
                    aDay.Archives.Add(new Arch(el, line, aDay));
                }
            }

            return ret;
        }

        /// <summary>Получение ID выбранной кнопки во временнОй линии</summary>
        int GetSelButtonInd(ToolStrip toolStrip)
        {
            for (int i = 0; i < toolStrip.Items.Count; i++)
                if ((tYears.Items[i] as ToolStripButton).Checked)
                    return i;
            return -1;
        }

        private void btnClose_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void btnRestore_Click(object sender, EventArgs e)
        {
            if (fDlg.ShowDialog() != System.Windows.Forms.DialogResult.OK) return;
            string restoreDir = fDlg.SelectedPath;

            SetLoadData(true, true);
            prBarRestore.Value = 0;
            prBarRestore.Visible = true;
            prBarRestore.BringToFront();
            Application.DoEvents();

            try
            {
                foreach (DirSync.FileCmp dir in SelArch.Sn.DList)
                {
                    if (!Directory.Exists(Path.Combine(restoreDir, dir.LocalPath)))
                        DirSync.SyncAction.CreateDirectoryWithCreateDate(dir.Path, Path.Combine(restoreDir, dir.LocalPath));
                }
                prBarRestore.Value = 10;
                for (int i = 0; i < SelArch.Sn.FList.Count; i++)
                {
                    string fPath = Path.Combine(restoreDir, SelArch.Sn.FList[i].LocalPath);
                    if (File.Exists(fPath)) File.SetAttributes(fPath, FileAttributes.Normal);
                    File.Copy(SelArch.Sn.FList[i].Path, fPath, true);
                    prBarRestore.Value = 10 + (int)((89.0 / (double)SelArch.Sn.FList.Count) * i);
                }
            }
            catch (Exception ex) { MessageBox.Show("Ошибка копирования файлов: " + ex.Message); }

            SetLoadData(false);
            prBarRestore.Visible = false;
            MessageBox.Show("Данные успешно восстановлены");
        }

    }
}
